<script setup lang="ts">
import RoomStatus from './components/RoomStatus.vue'
import RoomAction from './components/RoomAction.vue'
import RoomMessage from './components/RoomMessage.vue'

import { onMounted, onUnmounted } from 'vue'
// import type { Socket } from 'socket.io-client'
import { io, type Socket } from 'socket.io-client'
import { baseURL } from '@/utils/request'
import { useUserStore } from '@/stores'
import { useRoute } from 'vue-router'
import { MsgType, OrderType, SocketEventType } from '@/enums'
import type { Message, TimeMessages } from '@/types/room'
import { ref } from 'vue'
import { getConsultOrderDetail } from '@/services/consult'
import type { ConsultOrderItem, Image } from '@/types/consult'
import { nextTick } from 'vue'
import { showFailToast, showToast } from 'vant'
import dayjs from 'dayjs'
import { provide } from 'vue'

const userStore = useUserStore()
const route = useRoute()

let socket: Socket

/** 所有消息的列表 */
const messages = ref<Message[]>([])

onUnmounted(() => {
  // 组件卸载时，主动关闭连接
  socket?.close()
})

/** 是否为初始化加载 */
const isInitLoad = ref(true)

onMounted(() => {
  // 发起建立连接动作（这个动作是异步）
  socket = io(baseURL, {
    auth: {
      token: `Bearer ${userStore.user?.token}`
    },
    query: {
      orderId: route.query.orderId
    }
  })

  // 连接成功
  socket.on(SocketEventType.connect, () => {
    // 获取聊天记录（分页的），在连接成功后，socket会自动将历史聊天记录推送给我们
    socket.on(
      SocketEventType.chatMsgList,
      async ({ data }: { data: TimeMessages[] }) => {
        // data 是一个 消息分组 数组（按时间升序），每个 消息分组 里面又有很多消息
        console.log('[ 历史聊天记录 ] >', data)

        if (data.length <= 0) {
          showToast('没有聊天记录了')
          loading.value = false
          return
        }

        // 需要借助一个临时数组，来整理这一次加载的历史聊天记录
        const arr: Message[] = []

        // #region 循环每一个消息分组
        for (const timeMessages of data) {
          console.log('[ 每一个消息分组 ] >', timeMessages)

          const createTime = timeMessages.items[0].createTime

          // #region 手动组装一个分组时间通用消息
          // 在每个消息分组的消息列表追加之前，要先插入一个分组时间的通用消息，
          // 因为后端接口没有给我们弄这个消息，所以需要自己手动组装这个消息
          arr.push({
            msgType: MsgType.Notify,
            msg: { content: createTime },
            id: createTime,
            createTime: createTime
          })
          // #endregion

          // 将每一个消息分组中的所有消息先追加到这个临时数组 arr 中
          arr.push(...timeMessages.items)
        }

        // #region 图片消息对象 等图片加载完后 触发 van-image 组件的 load 事件时 是否滚动到底部
        // 如果不是第一次，那说明是第二次及之后
        if (!isInitLoad.value) {
          for (const v of arr) {
            if (v.msgType === MsgType.MsgImage) {
              // 标记这些消息对象不需要滚动到底部
              v.__noReachBottom = true
            }
          }
        }
        // #endregion

        // sort的回调函数会每次取两个元素，进行两两比较
        arr.sort((a, b) => {
          // 如果返回值 < 0    将 a 放在 b 之前
          // 如果返回值 === 0  不换位置
          // 如果返回值 > 0    将 a 放在 b 后面
          return (
            new Date(a.createTime).getTime() - new Date(b.createTime).getTime()
          )
        })

        // 等所有的消息分组都追加到临时数组 arr 中后，就可以一次性插入到真正的消息列表响应式数据 messages 中了
        // 为什么要用 unshift呢？
        // 因为 chatMsgList 事件有可能会触发多次，是分页加载的，下一页加载的消息的时间肯定在上一页之前
        // 分页加载的是更早的聊天记录，当然要插入到数组的前面啦
        messages.value.unshift(...arr)

        // 关闭下拉刷新的加载中状态
        loading.value = false

        // #region 滚动条的定位逻辑
        if (isInitLoad.value) {
          // 标记为不是第一次啦
          isInitLoad.value = false
          await nextTick() // 等到下次视图渲染完后再滚动到底部
          window.scrollTo(0, document.body.scrollHeight)
        } else {
          // 渲染之前的滚动条高度
          const oldHeight = document.body.scrollHeight
          await nextTick() // 等到下次视图渲染完后再滚动到底部
          // 渲染之后的滚动条高度
          const newHeight = document.body.scrollHeight

          // 将滚动条定到上一次的位置
          window.scrollTo(0, newHeight - oldHeight)
        }
        // #endregion

        // 后端接口逻辑：拿最后一个消息，标记为已读，之前的消息都会标记为已读
        socket.emit(
          SocketEventType.updateMsgStatus,
          messages.value[messages.value.length - 1].id
        )
      }
    )

    // 监听订单状态改变
    socket.on(SocketEventType.statusChange, (e) => {
      if (consult.value) {
        consult.value.status = e.status

        if (e.status === OrderType.ConsultChat) {
          getConsult()
        }
      }
    })

    // 监听接收消息
    socket.on(SocketEventType.receiveChatMsg, async (message: Message) => {
      messages.value.push(message)
      await nextTick() // 等到下次视图渲染完后再滚动到底部
      window.scrollTo(0, document.body.scrollHeight)

      // 将接收到的消息标记为已读
      socket.emit(SocketEventType.updateMsgStatus, message.id)
    })
  })

  socket.on(SocketEventType.error, (err) => {
    console.log('失败:', err)
    showFailToast(err.message)
  })

  socket.on(SocketEventType.disconnect, () => {
    console.log('连接已关闭')
  })
})

const consult = ref<ConsultOrderItem>()

provide('consult', consult)

provide('completeEva', (score: number) => {
  // 在函数内部把更新评论消息的逻辑写好，供别人来调用即可
  const message = messages.value.find((v) => v.msgType === MsgType.CardEvaForm)

  // 如果找到了
  if (message) {
    message.msgType = MsgType.CardEva
    message.msg.evaluateDoc = { score }
  }
})

function getConsult() {
  // 发请求获取订单详情
  return getConsultOrderDetail(route.query.orderId as string).then(
    (res: ConsultOrderItem) => {
      consult.value = res
    }
  )
}

getConsult()

const sendText = (text: string) => {
  console.log('[ text ] >', text)
  // TODO 通过socket发出这个文本消息
  socket.emit(SocketEventType.sendChatMsg, {
    from: userStore.user?.id, // 从谁发出来的消息
    to: consult.value?.docInfo?.id, // 发给谁
    msgType: MsgType.MsgText, // 消息类型
    // 消息主体
    msg: {
      content: text // 消息内容
    }
  })
}

const sendImage = (image: Image) => {
  socket.emit(SocketEventType.sendChatMsg, {
    from: userStore.user?.id,
    to: consult.value?.docInfo?.id,
    msgType: MsgType.MsgImage,
    msg: {
      picture: image
    }
  })
}

const loading = ref(false)

const onRefresh = () => {
  // Socket 服务 getChatMsgList 事件的：
  // 第一个参数：20 => 每页条数
  // 第二个参数：time => 从哪个时间点开始加载之前的聊天记录 => 当前已加载列表中最早的那一条消息的时间（createTime）
  // 第三个参数：订单id
  // 顺序能改变吗？不能改变，是后端已经规定好了的顺序
  socket.emit(
    SocketEventType.getChatMsgList,
    20,
    // 如果有第一条
    messages.value[0]
      ? // 那就拿第一条的时间
        messages.value[0].createTime
      : // 否则，拿当前时间
        dayjs().format('YYYY-MM-DD HH:mm:ss'),
    route.query.orderId
  )
}
</script>

<template>
  <div class="room-page">
    <cp-nav-bar title="问诊室" />
    <room-status
      :status="(consult?.status as number)"
      :countdown="(consult?.countdown as number)"
    ></room-status>

    <van-pull-refresh v-model="loading" @refresh="onRefresh">
      <room-message :list="messages"></room-message>
    </van-pull-refresh>

    <room-action
      :status="consult?.status"
      @send-text="sendText"
      @send-image="sendImage"
    ></room-action>
  </div>
</template>

<style lang="scss" scoped>
.room-page {
  padding-top: 90px;
  padding-bottom: 60px;
  min-height: 100vh;
  box-sizing: border-box;
  background-color: var(--cp-bg);
  .van-pull-refresh {
    width: 100%;
    min-height: calc(100vh - 150px);
  }
}
</style>
